"""
创建者：香农
********** V1 更新内容 20220930 **********
1、全自动交易
********** V2 更新内容 20221010 **********
1、优化空df判断
2、优化部成、部撤统计以及差异下单功能
3、优化执行完成后账户持仓总览
"""

import time
import datetime
import pandas as pd
import warnings

import random
from xtquant import xtdata  # 导入qmt库
from xtquant import xtconstant  # qmt常量
from xtquant.xttype import StockAccount  # 证券账户
from xtquant.xttrader import XtQuantTrader  # 交易接口

# ***************************************************  初始化  ***************************************************
warnings.filterwarnings('ignore')
pd.set_option('expand_frame_repr', False)  # 当列太多时不换行
pd.set_option('display.max_rows', 5000)  # 最多显示数据的行数
pd.set_option('display.float_format', lambda x: '%.2f' % x) # 调整展示精度
#pd.set_option('precision',6) # 展示小数位精度
#np.set_printoptions(suppress=True) # 不用科学计数法展示（暂时没用）
pd.set_option('display.unicode.ambiguous_as_wide', True) # 设置命令行输出时的列对齐功能
pd.set_option('display.unicode.east_asian_width', True)

# *************************************************** 设置参数 ***************************************************
path = 'D:\\中航证券QMT实盘-交易端\\userdata_mini'  # 极简版QMT的路径
user_id = '010400007679'
trade_plan_path = 'D:/python/xbx_stock_2021_part3/市值选谷策略深度优化实盘/program/调仓计划.xlsx'
daily_data_folder = 'D:/python策略模型开发/Datebase/1 股票/1 沪深股票/1 行情数据/XBX清洗' # 读取最新股票名称
order_time = str(datetime.datetime.now()).split(' ')[0].replace('-','') + '092501'
am_s = str(datetime.datetime.now()).split(' ')[0].replace('-','') + '093000'
am_e = str(datetime.datetime.now()).split(' ')[0].replace('-','') + '113000'
pm_s = str(datetime.datetime.now()).split(' ')[0].replace('-','') + '130000'
pm_e = str(datetime.datetime.now()).split(' ')[0].replace('-','') + '150000'
time_diff = 20 # 定义超时需撤单标准(单位：秒)

# *************************************************** 自定函数 ***************************************************
def book_inf():
    df = pd.read_excel(trade_plan_path)
    if df is not None:
        for i in range(len(df)):
            sub_id = xtdata.subscribe_quote(df['证券代码'].iloc[i], period = 'tick', count = -1)  # tick间隔最小是3s
            print(f"{df['证券代码'].iloc[i]} tick数据订阅成功")
    

def check_acc():
    acc = xt_trader.query_stock_asset(account)
    acc_df = pd.DataFrame()
    # acc_df.loc['0','账号类型'] = acc.account_type
    # acc_df.loc['0','资金账号'] = acc.account_id
    acc_df.loc['0','账户余额'] = acc.total_asset
    acc_df.loc['0','持仓市值'] = acc.market_value
    acc_df.loc['0','可用资金'] = acc.cash
    acc_df.loc['0','冻结资金'] = acc.frozen_cash
    return acc_df


def check_one_hold(stock_code):
    stock_info = xt_trader.query_stock_position(account, stock_code) # 查询单一股票持仓
    if stock_info:
        stock_df = pd.DataFrame()
        stock_df.loc['持仓','证券代码'] = stock_info.stock_code
        stock_df.loc['持仓','持仓成本'] = stock_info.open_price
        stock_df.loc['持仓','持仓股数'] = stock_info.volume
        stock_df.loc['持仓','在途股数'] = stock_info.on_road_volume
        stock_df.loc['持仓','可用股数'] = stock_info.can_use_volume
        stock_df.loc['持仓','冻结股数'] = stock_info.frozen_volume
        stock_df.loc['持仓','昨持股数'] = stock_info.yesterday_volume
        stock_df.loc['持仓','持仓市值'] = stock_info.market_value
        return stock_df


def check_all_hold():
    stocks_info = xt_trader.query_stock_positions(account) # 查询所有股票持仓
    if stocks_info:
        all_stock = pd.DataFrame()
        for stock in stocks_info:
            stock_temp = pd.DataFrame()
            stock_temp.loc['持仓','证券代码'] = stock.stock_code
            stock_temp.loc['持仓','证券名称'] = pd.read_csv(daily_data_folder + '/' + stock.stock_code + '.csv', parse_dates = ['交易日期'], encoding = "gbk")['股票名称'].iloc[-1]
            stock_temp.loc['持仓','持仓成本'] = stock.open_price
            stock_temp.loc['持仓','持仓股数'] = stock.volume
            stock_temp.loc['持仓','在途股数'] = stock.on_road_volume
            stock_temp.loc['持仓','可用股数'] = stock.can_use_volume
            stock_temp.loc['持仓','冻结股数'] = stock.frozen_volume
            stock_temp.loc['持仓','昨持股数'] = stock.yesterday_volume
            stock_temp.loc['持仓','持仓市值'] = stock.market_value
            all_stock = pd.concat([all_stock, stock_temp])
        return all_stock


def check_one_order_state(order_id):
    state = xt_trader.query_stock_order(account, order_id) # 查询单一订单状态
    if state:
        stock_state = pd.DataFrame()
        stock_state.loc['结果','下单时间'] = state.order_time
        stock_state.loc['结果','委托编号'] = str(state.order_id)
        stock_state.loc['结果','下单类型'] = stock_order_type_dict[state.order_type]
        stock_state.loc['结果','订单状态'] = order_state_dict[state.order_status]
        stock_state.loc['结果','证券代码'] = state.stock_code
        stock_state.loc['结果','下单价格'] = state.price
        stock_state.loc['结果','下单股数'] = state.order_volume
        stock_state.loc['结果','成交均价'] = state.traded_price
        stock_state.loc['结果','成交股数'] = state.traded_volume
        stock_state.loc['结果','订单标记'] = state.order_remark
        return stock_state


def check_all_order_state():
    orders_state = xt_trader.query_stock_orders(account)
    if orders_state:
        all_order_state = pd.DataFrame()
        for order in orders_state:
            order_temp = pd.DataFrame()
            order_temp.loc['结果','下单时间'] = order.order_time
            order_temp.loc['结果','委托编号'] = str(order.order_id)
            order_temp.loc['结果','下单类型'] = stock_order_type_dict[order.order_type]
            order_temp.loc['结果','订单状态'] = order_state_dict[order.order_status]
            order_temp.loc['结果','证券代码'] = order.stock_code
            order_temp.loc['结果','证券名称'] = pd.read_csv(daily_data_folder + '/' + order.stock_code + '.csv', parse_dates = ['交易日期'], encoding = "gbk")['股票名称'].iloc[-1]
            order_temp.loc['结果','下单价格'] = order.price
            order_temp.loc['结果','下单股数'] = order.order_volume
            order_temp.loc['结果','成交均价'] = order.traded_price
            order_temp.loc['结果','成交股数'] = order.traded_volume
            order_temp.loc['结果','订单标记'] = order.order_remark
            # order_temp.loc['结果','价格类型'] = order.price_type # 下单类型11限价，查询反馈接口是50错误，经夏普确认是QMT端生产接口问题
            order_temp['下单时间'] = order_temp['下单时间'].apply(lambda x: pd.to_datetime(x, unit='s') + pd.to_timedelta('8H'))
            all_order_state = pd.concat([all_order_state, order_temp])
            all_order_state.reset_index(inplace = True, drop = True)
        return all_order_state


order_state_dict = {48:'未报',
                    49:'待报',
                    50:'已报',
                    51:'已报待撤',
                    52:'部成待撤',
                    53:'部撤',
                    54:'已撤',
                    55:'部成',
                    56:'已成',
                    57:'废单',
                    255:'未知'
                    }


stock_order_type_dict = {23:'证券买入',
                         24:'证券卖出'
                         }


price_type_dict = {5:'市价',
                   11:'限价',
                   42:'上海最优五档即时成交剩余撤销',
                   43:'上海最优五档即时成交剩余转限价',
                   44:'深圳对手方最优价格',
                   45:'深圳本方最优价格',
                   46:'深圳即时成交剩余撤销',
                   47:'深圳最优五档即时成交剩余撤销',
                   48:'深圳全额成交或撤销'
                    }


def update_plan():
    plan = pd.read_excel(trade_plan_path)
    plan = plan[plan['调仓股数'] != 0]
    hold_list = check_all_hold()
    plan = pd.merge(plan, hold_list[['证券代码','持仓股数','可用股数']], on = ['证券代码'], how = 'left')
    plan = plan[['证券代码','证券名称','持仓股数','可用股数','调仓股数']]
    plan['持仓股数'].fillna(value = 0, inplace = True)
    plan['可用股数'].fillna(value = 0, inplace = True)
    plan['调仓进度'] = '未完成'
    plan['挂单次数'] = 0
    plan['已成股数'] = 0
    plan['未成股数'] = 0
    
    order_list = check_all_order_state()
    if order_list is not None:
        for i in range(len(order_list)):
            if order_list['下单类型'].iloc[i] == '证券买入' and order_list['订单状态'].iloc[i] == '已报':
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '未成股数'] += order_list['下单股数'].iloc[i]
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '挂单次数'] += 1
            if order_list['下单类型'].iloc[i] == '证券买入' and order_list['订单状态'].iloc[i] == '部撤':
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '已成股数'] += order_list['成交股数'].iloc[i]
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '挂单次数'] += 1
            if order_list['下单类型'].iloc[i] == '证券买入' and order_list['订单状态'].iloc[i] == '部成':
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '已成股数'] += order_list['成交股数'].iloc[i]
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '未成股数'] += order_list['下单股数'].iloc[i] - order_list['成交股数'].iloc[i]
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '挂单次数'] += 1
            if order_list['下单类型'].iloc[i] == '证券买入' and order_list['订单状态'].iloc[i] == '已成':
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '已成股数'] += order_list['成交股数'].iloc[i]
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '挂单次数'] += 1
            if order_list['下单类型'].iloc[i] == '证券买入' and order_list['订单状态'].iloc[i] == '已撤':
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '挂单次数'] += 1
            
            if order_list['下单类型'].iloc[i] == '证券卖出' and order_list['订单状态'].iloc[i] == '已报':
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '未成股数'] -= order_list['下单股数'].iloc[i]
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '挂单次数'] += 1
            if order_list['下单类型'].iloc[i] == '证券卖出' and order_list['订单状态'].iloc[i] == '部撤':
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '已成股数'] -= order_list['成交股数'].iloc[i]
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '挂单次数'] += 1
            if order_list['下单类型'].iloc[i] == '证券卖出' and order_list['订单状态'].iloc[i] == '部成':
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '已成股数'] -= order_list['成交股数'].iloc[i]
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '未成股数'] -= order_list['下单股数'].iloc[i] - order_list['成交股数'].iloc[i]
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '挂单次数'] += 1
            if order_list['下单类型'].iloc[i] == '证券卖出' and order_list['订单状态'].iloc[i] == '已成':
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '已成股数'] -= order_list['成交股数'].iloc[i]
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '挂单次数'] += 1
            if order_list['下单类型'].iloc[i] == '证券卖出' and order_list['订单状态'].iloc[i] == '已撤':
                plan.loc[plan['证券代码'] == order_list['证券代码'].iloc[i], '挂单次数'] += 1
    plan.loc[-plan['调仓股数'] > plan['可用股数'] + abs(plan['未成股数']), '调仓进度'] = '持仓不足'
    plan.loc[plan['调仓股数'] == plan['已成股数'], '调仓进度'] = 'Done'
    return plan


def execute_plan():
    if datetime.datetime.now() >= pd.to_datetime(am_s): # 限定启动撤单机制时间段，避免撤销0925-0930期间有效挂单(QMT支持非交易时段提交撤单)
        plan = pd.read_excel(trade_plan_path)
        plan = plan[plan['调仓股数'] != 0]
        order_list = check_all_order_state()
        if order_list is not None:
            order_list['时间差'] = order_list['下单时间'] + pd.Timedelta(seconds = time_diff)
            overtime_list = order_list[order_list['时间差'] < datetime.datetime.now()]
            overtime_list= overtime_list[overtime_list['订单状态'].isin(['已报','部成'])]
            if not overtime_list.empty:
                print(f'<1> 以下 {len(overtime_list)} 笔挂单已超时效，撤单后重新下单：\n{overtime_list}\n')
                for i in range(len(overtime_list)):
                    cancel_result = xt_trader.cancel_order_stock(account, int(overtime_list['委托编号'].iloc[i])) # 撤单
                    if cancel_result == 0:
                        print(f"({i}) {overtime_list['委托编号'].iloc[i]}撤单成功：{overtime_list['证券代码'].iloc[i]} {overtime_list['证券名称'].iloc[i]} {overtime_list['下单类型'].iloc[i]} {format(overtime_list['下单股数'].iloc[i],'.0f')} 股")
                    if cancel_result == -1:
                        print(f"({i}) {overtime_list['委托编号'].iloc[i]}撤单失败：{overtime_list['证券代码'].iloc[i]} {overtime_list['证券名称'].iloc[i]} {overtime_list['下单类型'].iloc[i]} {format(overtime_list['下单股数'].iloc[i],'.0f')} 股")
                time.sleep(4) # 等QMT端完成后台撤单更新
            else:
                print('<1> 暂无超时挂单：')
        else:
            print('<1> 暂无挂单：')
    else:
        print(f'<1> 未到自动撤单机制启动时间 {pd.to_datetime(am_s)}')

    
    exe_plan = update_plan()
    print(f'\n<2> 执行进度：\n{exe_plan}\n')
    exe_plan = exe_plan[exe_plan['调仓进度'] == '未完成']
    if not exe_plan.empty:
        print('<3> 开始自动挂单：')
        for i in range(len(exe_plan)):
            if abs(exe_plan['调仓股数'].iloc[i]) - exe_plan['已成股数'].iloc[i] - exe_plan['未成股数'].iloc[i] > 0:
                exe_num = exe_plan['调仓股数'].iloc[i] - exe_plan['已成股数'].iloc[i] - exe_plan['未成股数'].iloc[i]
                if exe_num > 0:
                    exe_price = xtdata.get_full_tick([exe_plan['证券代码'].iloc[i]])[exe_plan['证券代码'].iloc[i]]['lastPrice'] + exe_plan['挂单次数'].iloc[i] * 0.01
                    order_result = xt_trader.order_stock(account, exe_plan['证券代码'].iloc[i], xtconstant.STOCK_BUY, int(exe_num), 11, exe_price, '香农API', '自动买入')
                    if order_result >= 0:
                        print(f"({i}) 下单成功：{exe_plan['证券代码'].iloc[i]} {exe_plan['证券名称'].iloc[i]} 以 {exe_price} 买入 {format(exe_num,'.0f')} 股")
                    if order_result == -1:
                        print(f"({i}) 下单失败：{exe_plan['证券代码'].iloc[i]} {exe_plan['证券名称'].iloc[i]} 以 {exe_price} 买入 {format(exe_num,'.0f')} 股")
                if exe_num < 0:
                    exe_price = xtdata.get_full_tick([exe_plan['证券代码'].iloc[i]])[exe_plan['证券代码'].iloc[i]]['lastPrice'] - exe_plan['挂单次数'].iloc[i] * 0.01
                    order_result = xt_trader.order_stock(account, exe_plan['证券代码'].iloc[i], xtconstant.STOCK_SELL, int(-exe_num), 11, exe_price, '香农API', '自动卖出')
                    if order_result >= 0:
                        print(f"({i}) 下单成功：{exe_plan['证券代码'].iloc[i]} {exe_plan['证券名称'].iloc[i]} 以 {format(exe_price,'.2f')} 卖出 {format(-exe_num,'.0f')} 股")
                    if order_result == -1:
                        print(f"({i}) 下单失败：{exe_plan['证券代码'].iloc[i]} {exe_plan['证券名称'].iloc[i]} 以 {format(exe_price,'.2f')} 卖出 {format(-exe_num,'.0f')} 股")
        return 0
    else:
        return 1


def num_to_pct(value):
    return '%.2f%%' % (value * 100)

# *************************************************** 主体程序 ***************************************************
session_id = random.randint(1, 1000000) # session_id为会话编号，策略使用方对于不同的Python策略需要使用不同的会话编号（自己随便写）
xt_trader = XtQuantTrader(path, session_id) # 创建API实例
account = StockAccount(user_id, 'STOCK') # 创建股票账户
xt_trader.start() # 启动交易线程
connect_result = xt_trader.connect() 
print('链接成功' if connect_result == 0 else '链接失败') # 建立交易连接，返回0表示连接成功
subscribe_result = xt_trader.subscribe(account)
print('订阅成功' if subscribe_result == 0 else '订阅失败') # 订阅账户信息，返回0表示订阅成功
book_inf()


t1 = datetime.datetime.now()
print(f"{'*'*65} 基础信息 {'*'*65}\n<1> 账户总览：\n{check_acc()}\n\n<2> 持仓总览：\n{check_all_hold()}\n\n<3> 挂单总览：\n{check_all_order_state()}")

plan_0 = update_plan()
mess ='无异常'
if len(plan_0[~plan_0['调仓进度'].isin(['未完成','Done'])]) > 0:
    mess = f"{len(plan_0[~plan_0['调仓进度'].isin(['未完成','Done'])])} 笔调仓异常，请注意检查调仓计划"
print(f"\n{'*'*65}  调仓预检 0 {'*'*65}\n<1> 调仓预览：{mess}\n{plan_0}\n")

n = 1
finish = 0
while finish == 0:
    try:
        if datetime.datetime.now() >= pd.to_datetime(order_time):
            print('\n', '*'*65, '执行调仓', n, '*'*65)
            finish = execute_plan()
            n += 1
            time.sleep(time_diff/2)
        else:
            print(f'未到执行下单时间 {pd.to_datetime(order_time)}，5秒后重试')
            time.sleep(5)
    except Exception as e:
        print('功能异常，请人工介入，报错内容：%s' % e)
        time.sleep(30)

holding_stock = check_all_hold()
holding_stock['持仓占比'] = (holding_stock['持仓市值'] / check_acc()['账户余额'].iloc[0]).apply(num_to_pct)
print(f"{'*'*66} 调仓已完成 {'*'*66}\n<1> 账户总览：\n{check_acc()}\n\n<2> 最新持仓总览：\n{holding_stock.sort_values(['持仓市值'], ascending = False)}")

# xt_trader.stop() # 停止交易线程
t2 = datetime.datetime.now()
print(f"\n********** 全部完成 **********\n开始时间：{str(t1)[:-7]}\n结束时间：{str(t2)[:-7]}\n合计耗时：{str(t2-t1)[:-7]}")

